# -- coding: utf-8 --

import sys
from time import sleep

import cv2
import ctypes
from ctypes import *
import numpy as np
try:
    from common.utils.NovishLogger import logger
except ImportError:
    from loguru import logger

sys.path.append(r".\MvImport")

from MvCameraControl_class import *

g_bExit = False


# 是否是Mono图像
def Is_mono_data(enGvspPixelType):
    if PixelType_Gvsp_Mono8 == enGvspPixelType or PixelType_Gvsp_Mono10 == enGvspPixelType \
            or PixelType_Gvsp_Mono10_Packed == enGvspPixelType or PixelType_Gvsp_Mono12 == enGvspPixelType \
            or PixelType_Gvsp_Mono12_Packed == enGvspPixelType:
        return True
    else:
        return False


# 是否是彩色图像
def Is_color_data(enGvspPixelType):
    if PixelType_Gvsp_BayerGR8 == enGvspPixelType or PixelType_Gvsp_BayerRG8 == enGvspPixelType \
            or PixelType_Gvsp_BayerGB8 == enGvspPixelType or PixelType_Gvsp_BayerBG8 == enGvspPixelType \
            or PixelType_Gvsp_BayerGR10 == enGvspPixelType or PixelType_Gvsp_BayerRG10 == enGvspPixelType \
            or PixelType_Gvsp_BayerGB10 == enGvspPixelType or PixelType_Gvsp_BayerBG10 == enGvspPixelType \
            or PixelType_Gvsp_BayerGR12 == enGvspPixelType or PixelType_Gvsp_BayerRG12 == enGvspPixelType \
            or PixelType_Gvsp_BayerGB12 == enGvspPixelType or PixelType_Gvsp_BayerBG12 == enGvspPixelType \
            or PixelType_Gvsp_BayerGR10_Packed == enGvspPixelType or PixelType_Gvsp_BayerRG10_Packed == enGvspPixelType \
            or PixelType_Gvsp_BayerGB10_Packed == enGvspPixelType or PixelType_Gvsp_BayerBG10_Packed == enGvspPixelType \
            or PixelType_Gvsp_BayerGR12_Packed == enGvspPixelType or PixelType_Gvsp_BayerRG12_Packed == enGvspPixelType \
            or PixelType_Gvsp_BayerGB12_Packed == enGvspPixelType or PixelType_Gvsp_BayerBG12_Packed == enGvspPixelType \
            or PixelType_Gvsp_YUV422_Packed == enGvspPixelType or PixelType_Gvsp_YUV422_YUYV_Packed == enGvspPixelType:
        return True
    else:
        return False


class HkCameraWrapper:
    def __init__(self, cam_sn, ipc_cam_conf):
        self.cam_sn = cam_sn
        self.ipc_cam_conf = ipc_cam_conf
        self.frame = None
        self.g_bExit = False
        self.cam = MvCamera()
        self.conf_file_path = ipc_cam_conf

    def fail_handle(self):
        return f'相机{self.cam_sn}出错'

    def init_cam(self, serial_number=None, conf_file_path=None):
        """初始化相机，支持从配置文件加载序列号"""
        if serial_number is None:
            logger.debug("No serial number provided or found in configuration.")
            self.fail_handle()

        # ch:初始化SDK | en: initialize SDK
        ret = MvCamera.MV_CC_Initialize()
        if ret != 0:
            logger.debug("initialize SDK fail! ret[0x%x]" % ret)
            self.fail_handle()

        deviceList = MV_CC_DEVICE_INFO_LIST()
        tlayerType = (MV_GIGE_DEVICE | MV_USB_DEVICE | MV_GENTL_CAMERALINK_DEVICE
                      | MV_GENTL_CXP_DEVICE | MV_GENTL_XOF_DEVICE)

        # ch:枚举设备 | en:Enum device
        ret = MvCamera.MV_CC_EnumDevices(tlayerType, deviceList)
        if ret != 0:
            logger.debug("enum devices fail! ret[0x%x]" % ret)
            self.fail_handle()

        if deviceList.nDeviceNum == 0:
            logger.debug("find no device!")
            self.fail_handle()

        logger.debug("Find %d devices!" % deviceList.nDeviceNum)

        # serial_number = input("please input the serial number of the device to connect:")

        stDeviceList = None
        for i in range(0, deviceList.nDeviceNum):
            mvcc_dev_info = cast(deviceList.pDeviceInfo[i], POINTER(MV_CC_DEVICE_INFO)).contents
            strSerialNumber = ""
            if mvcc_dev_info.nTLayerType == MV_USB_DEVICE:
                for per in mvcc_dev_info.SpecialInfo.stUsb3VInfo.chSerialNumber:
                    if per == 0:
                        break
                    strSerialNumber = strSerialNumber + chr(per)
            elif mvcc_dev_info.nTLayerType == MV_GIGE_DEVICE or mvcc_dev_info.nTLayerType == MV_GENTL_GIGE_DEVICE:
                for per in mvcc_dev_info.SpecialInfo.stGigEInfo.chSerialNumber:
                    if per == 0:
                        break
                    strSerialNumber = strSerialNumber + chr(per)
            elif mvcc_dev_info.nTLayerType == MV_GENTL_CAMERALINK_DEVICE:
                for per in mvcc_dev_info.SpecialInfo.stCMLInfo.chSerialNumber:
                    if per == 0:
                        break
                    strSerialNumber = strSerialNumber + chr(per)
            elif mvcc_dev_info.nTLayerType == MV_GENTL_CXP_DEVICE:
                for per in mvcc_dev_info.SpecialInfo.stCXPInfo.chSerialNumber:
                    if per == 0:
                        break
                    strSerialNumber = strSerialNumber + chr(per)
            elif mvcc_dev_info.nTLayerType == MV_GENTL_XOF_DEVICE:
                for per in mvcc_dev_info.SpecialInfo.stXoFInfo.chSerialNumber:
                    if per == 0:
                        break
                    strSerialNumber = strSerialNumber + chr(per)
            logger.debug(strSerialNumber)
            if strSerialNumber == serial_number:
                stDeviceList = cast(deviceList.pDeviceInfo[i], POINTER(MV_CC_DEVICE_INFO)).contents
                break

        if stDeviceList is None:
            logger.debug("Device with serial number %s not found!" % serial_number)
            self.fail_handle()

        # ch:创建相机实例 | en:Creat Camera Object
        # cam = MvCamera()

        # ch:选择设备并创建句柄 | en:Select device and create handle
        ret = self.cam.MV_CC_CreateHandle(stDeviceList)
        if ret != 0:
            logger.debug("create handle fail! ret[0x%x]" % ret)
            self.fail_handle()

        # ch:打开设备 | en:Open device
        ret = self.cam.MV_CC_OpenDevice(MV_ACCESS_Exclusive, 0)
        if ret != 0:
            logger.debug("open device fail! ret[0x%x]" % ret)
            self.fail_handle()

        ret = self.load_conf(conf_file_path)
        if ret != 0:
            logger.debug(f"load feature fail! ret [0x{ret:x}]")
            # self.fail_handle()
        # ch:探测网络最佳包大小(只对GigE相机有效) | en:Detection network optimal package size(It only works for the GigE camera)
        if stDeviceList.nTLayerType == MV_GIGE_DEVICE or stDeviceList.nTLayerType == MV_GENTL_GIGE_DEVICE:
            nPacketSize = self.cam.MV_CC_GetOptimalPacketSize()
            if int(nPacketSize) > 0:
                ret = self.cam.MV_CC_SetIntValue("GevSCPSPacketSize", nPacketSize)
                if ret != 0:
                    logger.debug("Warning: Set Packet Size fail! ret[0x%x]" % ret)
            else:
                logger.debug("Warning: Get Packet Size fail! ret[0x%x]" % nPacketSize)

        stBool = c_bool(False)
        ret = self.cam.MV_CC_GetBoolValue("AcquisitionFrameRateEnable", stBool)
        if ret != 0:
            logger.debug("get AcquisitionFrameRateEnable fail! ret[0x%x]" % ret)

        # ch:设置触发模式为off | en:Set trigger mode as off
        ret = self.cam.MV_CC_SetEnumValue("TriggerMode", MV_TRIGGER_MODE_OFF)
        if ret != 0:
            logger.debug("set trigger mode fail! ret[0x%x]" % ret)
            self.fail_handle()

        # ch:开始取流 | en:Start grab image
        ret = self.cam.MV_CC_StartGrabbing()
        if ret != 0:
            logger.debug("start grabbing fail! ret[0x%x]" % ret)
            self.fail_handle()

    def load_conf(self, conf_file: str):
        if conf_file is not None:
            load_conf_result = self.cam.MV_CC_FeatureLoad(conf_file)
            if load_conf_result != OK:
                logger.error(f'hk cam Load Conf {conf_file} Failed, load_conf_result:{load_conf_result}')
                return f'相机{self.cam_sn}配置文件有误'
            else:
                logger.info(f'hk cam Load Conf {conf_file} success')
        if conf_file is None:
            logger.debug("No conf file provided")
            return -1
        logger.debug(f"Loading configuration from {conf_file}")
        return self.cam.MV_CC_FeatureLoad(conf_file)

    def save_conf(self, conf_file: str):
        return self.cam.MV_CC_FeatureSave(conf_file)

    def get_img(self):
        stOutFrame = MV_FRAME_OUT()
        memset(byref(stOutFrame), 0, sizeof(stOutFrame))
        ret = self.cam.MV_CC_GetImageBuffer(stOutFrame, 1000)
        logger.debug(f"ret:{ret}")

        if stOutFrame.pBufAddr and 0 == ret:
            logger.debug("get one frame: Width[%d], Height[%d], nFrameNum[%d]" % (
                stOutFrame.stFrameInfo.nWidth, stOutFrame.stFrameInfo.nHeight, stOutFrame.stFrameInfo.nFrameNum))

            # 确保 nWidth 和 nHeight 是整数
            nWidth = int(stOutFrame.stFrameInfo.nWidth)
            nHeight = int(stOutFrame.stFrameInfo.nHeight)

            # 检查 nWidth 和 nHeight 是否为正整数
            if nWidth <= 0 or nHeight <= 0:
                logger.debug("Invalid width or height: Width[%d], Height[%d]" % (nWidth, nHeight))
                return None

            # 检查 pBufAddr 是否为有效的地址
            if not isinstance(stOutFrame.pBufAddr, ctypes.POINTER(ctypes.c_ubyte)) or stOutFrame.pBufAddr == 0:
                logger.debug("Invalid buffer address: pBufAddr[0x%x]" % ctypes.addressof(stOutFrame.pBufAddr.contents))
                return None

            # 获取像素类型
            nPixelType = stOutFrame.stFrameInfo.enPixelType
            logger.debug(f"PixelType_Gvsp_BGR8_Packed: {PixelType_Gvsp_BGR8_Packed}; nPixelType: {nPixelType}")

            if nPixelType == PixelType_Gvsp_BGR8_Packed:
                nChannel = 3
                frame_data = (ctypes.c_ubyte * (nWidth * nHeight * nChannel)).from_address(
                    ctypes.addressof(stOutFrame.pBufAddr.contents))
                frame = np.ctypeslib.as_array(frame_data).reshape(nHeight, nWidth, nChannel)

            elif nPixelType == PixelType_Gvsp_YUV422_Packed:
                # 将图像数据转换为OpenCV格式
                frame_data = (ctypes.c_ubyte * (nWidth * nHeight * 2)).from_address(
                    ctypes.addressof(stOutFrame.pBufAddr.contents))
                frame = np.ctypeslib.as_array(frame_data).reshape(nHeight, nWidth, 2)
                # 将 YUYV 转换为 BGR
                # frame = cv2.cvtColor(frame, cv2.COLOR_YUV2BGR_YUYV)
                frame = cv2.cvtColor(frame, cv2.COLOR_YUV2BGR_Y422)
            elif nPixelType == PixelType_Gvsp_BayerRG8:
                frame_data = (ctypes.c_ubyte * (nWidth * nHeight)).from_address(
                    ctypes.addressof(stOutFrame.pBufAddr.contents))
                frame = np.ctypeslib.as_array(frame_data).reshape(nHeight, nWidth)
                frame = cv2.cvtColor(frame, cv2.COLOR_BayerRG2RGB)
            elif Is_mono_data(nPixelType):
                frame_data = (ctypes.c_ubyte * (nWidth * nHeight)).from_address(
                    ctypes.addressof(stOutFrame.pBufAddr.contents))
                frame = np.ctypeslib.as_array(frame_data).reshape(nHeight, nWidth)
            else:
                logger.debug("Unsupported pixel type: 0x%x" % nPixelType)
                return None

            nRet = self.cam.MV_CC_FreeImageBuffer(stOutFrame)
            return frame
        else:
            logger.debug("no data[0x%x]" % ret)
            return None

    def stop_camera(self):
        self.g_bExit = True

        # ch:停止取流 | en:Stop grab image
        ret = self.cam.MV_CC_StopGrabbing()
        if ret != 0:
            logger.debug("stop grabbing fail! ret[0x%x]" % ret)
            sys.exit()

        # ch:关闭设备 | Close device
        ret = self.cam.MV_CC_CloseDevice()
        if ret != 0:
            logger.debug("close device fail! ret[0x%x]" % ret)
            sys.exit()

        # ch:销毁句柄 | Destroy handle
        ret = self.cam.MV_CC_DestroyHandle()
        if ret != 0:
            logger.debug("destroy handle fail! ret[0x%x]" % ret)
            sys.exit()

        # ch:反初始化SDK | en: finalize SDK
        MvCamera.MV_CC_Finalize()


OK = 0  # 成功


def example_usage():
    serial_num = "00D87499379"
    cam_conf_path = f"resource/{serial_num}.mfs"
    camera = HkCameraWrapper(serial_num, cam_conf_path)
    camera.init_cam(serial_num, cam_conf_path)
    # serial_num2 = "00F07261521"
    # camera2 = Camera(f"resource/{serial_num2}.mfs")
    # camera2.initialize_camera(serial_num2)

    try:
        while True:
            frame = camera.get_img()
            # frame2 = camera2.get_img()
            if frame is not None:
                resized_img = cv2.resize(frame, (640, 480))
                cv2.imshow('Frame', resized_img)
                # cv2.imshow('Frame2', frame2)
                if cv2.waitKey(1) & 0xFF == ord('q'):
                    break
            sleep(1)
    finally:
        camera.stop_camera()
        cv2.destroyAllWindows()


if __name__ == "__main__":
    example_usage()
